﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace DirectX_CS
{
	public partial class Mouse : Form , INamed
	{
		private static List<Mouse> myMouseForms = null;

		private static Mouse selectedForm = null;

		protected string shortName = "Mouse";
		[PGBrowsable]
		public string ShortName
		{
			get { return shortName; }
			set { shortName = value; }
		}
		private int shortNumber = 0;

		public int ShortNumber
		{
			get { return shortNumber; }
			set { shortNumber = value; }
		}
		[PGBrowsable]
		public string ObjectName
		{
			get { return shortName + " " + shortNumber; }
		}
		[PGBrowsable]
		public static Mouse OnlyMouse
		{
			get { return Mouse.selectedForm; }
			set { Mouse.selectedForm = value; }
		}
		[PGBrowsable]
		public static List<Mouse> MouseForms
		{
			get { return Mouse.myMouseForms; }
			set { Mouse.myMouseForms = value; }
		}

		private Form mDirectXWindow;

		public Form DXForm
		{
			get { return mDirectXWindow; }
			set { mDirectXWindow = value; }
		}
		public Mouse(Form fWithEventHandlers)
		{
			mDirectXWindow = fWithEventHandlers;
			InitializeComponent();

			//take care of static objects
			if (myMouseForms == null)
				myMouseForms = new List<Mouse>();
			myMouseForms.Add(this);

			if (selectedForm == null)
				selectedForm = this;

			SetPGBrowsableAttributes();

			//attach mouse handlers to window
			DXForm.MouseMove += new MouseEventHandler(mFrm_MouseMove);
			DXForm.MouseUp += new MouseEventHandler(mFrm_MouseUp);
			DXForm.MouseDown += new MouseEventHandler(mFrm_MouseDown);
			DXForm.MouseWheel += new MouseEventHandler(mFrm_MouseWheel);

		}

		//one mouse handler for all mouse functions...
		public static event System.Windows.Forms.MouseEventHandler MouseEvent;
		//[PGBrowsable]
		//public bool Enabled
		//{
		//    get { return this == selectedForm; }
		//}
		private void SetPGBrowsableAttributes()
		{
			this.pgMouse.BrowsableAttributes = new System.ComponentModel.AttributeCollection(new PGBrowsableAttribute());
		}

		private float dMouseRho = .10f;
		[PGBrowsable]
		public float MouseRhoScalingFactor
		{
			get { return dMouseRho; }
			set { dMouseRho = value; }
		}

		public float abs(float f)
		{
			return (f <= 0 ? -f : f);
		}

		private Vector3 SelectedLookAtPosition
		{
			get{return Camera.DefaultCamera.CameraTarget;}
		}
		private MouseButtons lastButton = MouseButtons.None;

		protected float pixelsPerUnit = 1.0f;
		[PGBrowsable]
		public float PixelsPerUnits
		{
			get { return pixelsPerUnit; }
			set { pixelsPerUnit = value; }
		}
		[PGBrowsable]
		public float UnitsPerPixel
		{
			get { return 1f / pixelsPerUnit; }
			set { pixelsPerUnit = 1f / value; }

		}
		//[PGBrowsable]
		//public Vector3 SelectedObject_Unproject
		//{
		//    get
		//    {
		//        //SelectMesh onlyObject = SelectMesh.OnlyObject;
		//        //if (onlyObject == null)
		//        //    return new Vector3(0, 0, 0);
		//        //else
		//        //{
		//            Vector3 unprojection = Mouse.UnprojectObject(mouseLast);// MouseUnprojection(mouseLast);
		//            return unprojection;
		//    }
		//}
		//[PGBrowsable]
		//public Vector3 SelectedObject_MouseRay
		//{
		//    get
		//    {
		//        Device d = MainProgram.DxDevice;
		//        return UnprojectRay(d.Viewport,
		//            new Vector3(mouseLast.X, mouseLast.Y, 0),
		//            d.Transform.Projection,
		//            d.Transform.View,
		//            SelectedObjectWorld);

		//    }
		//}
		public Vector3 SelectMesh_Intersection
		{
			get
			{				    
				
				Device dev = MainProgram.DxDevice;
				Matrix world = SelectedObjectWorld;//dev.Transform.World;

				Vector3 farPoint = new Vector3(mouseLast.X, mouseLast.Y, MainProgram.OnlyProgram.ProjectionMaxZ);
				Vector3 nearPoint = new Vector3(mouseLast.X, mouseLast.Y, MainProgram.OnlyProgram.ProjectionMinZ);

				Device d = MainProgram.DxDevice;
				//world = Matrix.Identity; //test
				Vector3 unproject_Near = Vector3.Unproject(nearPoint, d.Viewport, d.Transform.Projection, d.Transform.View, world); //(ref MousePoint, ref world, out farPoint, out nearPoint);
				Vector3 unproject_Far = Vector3.Unproject(farPoint, d.Viewport,  d.Transform.Projection, d.Transform.View, world); //(ref MousePoint, ref world, out farPoint, out nearPoint);
				
				Vector3 pos = SelectMesh.OnlyObject.IntersectBoundedMesh(ref unproject_Far, ref unproject_Near);
				return pos;
			}
		}
		public static Matrix SelectedObjectWorld
		{
			get
			{
				Device dev = MainProgram.DxDevice;
				Matrix world = dev.Transform.World;
				object selectedPrim = ObjectsEditor.Selected;
				if (selectedPrim != null)
				{
					if (selectedPrim is IModelTransformable)
					{
						IModelTransformable mv = (IModelTransformable)selectedPrim;
						
						TransformMatrix mvm = mv.ModelView;
						if (mvm != null)
							world = mvm.RecreateMatrix();
					}
				}
				return world;
			}
		}
		/*
		public static Vector3 UnprojectObject(Point MousePoint)
		{
			Device dev = MainProgram.DxDevice;
			Matrix world = SelectedObjectWorld;//dev.Transform.World;


			Vector3 farPoint = new Vector3(MousePoint.X, MousePoint.Y, 0);
			Vector3 nearPoint = new Vector3(MousePoint.X, MousePoint.Y, MainProgram.OnlyProgram.ProjectionMinZ);

			Device d = MainProgram.DxDevice;
			//world = Matrix.Identity; //test
			Vector3 unproject = Mouse.UnprojectEx(d.Viewport, nearPoint, d.Transform.Projection, d.Transform.View, world); //(ref MousePoint, ref world, out farPoint, out nearPoint);
			return unproject;
		}

		 ///old code to attempt to reinvent the wheel regarding a supposedly faulty Unproject method of the vector3 class.
	
		private static bool useDXCode = true;
		public static Vector3 UnprojectEx(Viewport viewport, Vector3 screenSpace, Matrix projection, Matrix view, Matrix world)
			{
				//First, convert raw screen coords to unprojectable ones 
				Vector3 position = new Vector3();
				position.X = (screenSpace.X - (float)viewport.X) / (float)viewport.Width;
				float diffY = viewport.Height - screenSpace.Y - (float)viewport.Y;
				position.Y = diffY/ (float)viewport.Height;
				position.Z = (screenSpace.Z - viewport.MinZ) / (viewport.MaxZ - viewport.MinZ);

				if (useDXCode)
				{
					Vector3 dxUP3 = Vector3.Unproject(position, viewport, projection, view, world);
					return dxUP3;
				}

				//Unproject by transforming the 4d vector by the inverse of the projecttion matrix, followed by the inverse of the view matrix.   
				Vector4 us4 = new Vector4(position.X, position.Y, position.Z, 1f);
				Matrix preInverse = Matrix.Multiply(world, view);//, projection);
				preInverse = Matrix.Multiply(preInverse, projection);
				Matrix inverse =  Matrix.Invert(preInverse);
				Vector4 up4 = Vector4.Transform(us4, inverse);
				Vector3 up3 = new Vector3(up4.X, up4.Y, up4.Z);
				if (up4.W != 0)
				    up3 = up3  * (1f / up4.W); //better to do this here to reduce precision loss..   
				return up3;
			}
		//And this method is very useful! It may be the source of some people's problems:

		public static Vector3 UnprojectRay(Viewport viewport, Vector3 screenSpace, Matrix projection, Matrix view, Matrix world)
			{

		    Vector3 screenNear = new Vector3(screenSpace.X, screenSpace.Y, 0f);
		    Vector3 screenFar = new Vector3(screenSpace.X,screenSpace.Y , 1f);
		    //multiplying by 10 makes sure the line's direction is accurate - by casting out   
		    //a very far distance. The direction is all we really care about, so the further  
		    //away the point, the more accurate it will be....  
		    Vector3 worldNear = UnprojectEx(viewport, screenNear, projection, view, world)  ;
		    Vector3 worldFar = UnprojectEx(viewport, screenFar, projection, view, world)  ;
		    Vector3 direction = worldFar - worldNear;
		    float len = 1f / direction.Length();
		    direction = direction * len; //manual normalisation  

		    return direction;
		}
		 * 
		 * 
	*/

		//[PGBrowsable]
		//public float PixelsPerRadian
		//{
		//    get { return pixelsPerRadian; }
		//    set { pixelsPerRadian = value; }
		//}
		[PGBrowsable]

		protected float radianFactor = 1f;
		public float RadianFactor
		{
			get { return 1f ; }
			set { radianFactor = value; }
		}

		public void mFrm_MouseUp(object sender, MouseEventArgs e)
		{
			lastButton = MouseButtons.None;
			FireMouseEvent(e);
		}

		private Point mouseLast = new Point(0, 0); //set when mouse is moved inside the client area presumably before the mouse move comes when a button is being pressed.

		//should set position around a boundedMesh circle, or boundedMesh sphere.
		//spherical coordinates appropriate here.

		public bool MoveCameraPositionWithMouse
		{
			get { return cbMoveCameraWithMouse.Checked; }
			set {
				cbMoveCameraWithMouse.Checked = value;
			}
		}

		public void mFrm_MouseMove(object sender, MouseEventArgs e)
		{
			if (MoveCameraPositionWithMouse)
			{
				int dx = e.X - mouseLast.X;
				int dy = e.Y - mouseLast.Y;
				int ds = dx * dx + dy * dy;

				if (dx == 0 && dy == 0)
				{
				}
				else
				{
					if (e.Button == MouseButtons.Left)
					{
						Vector3 oCP = Camera.SelectedCameraPosition;
						//Vector3 oCP = SelectedLookAtPosition;
						Vector3 nCp = new Vector3(oCP.X + dx * pixelsPerUnit, oCP.Y, oCP.Z + dy * pixelsPerUnit);
						Camera.SelectedCameraPosition = nCp;
						Camera.Selected.UpdateCameraPositionControls();
						Camera.Selected.SetViewTransform();
					}
					else if (e.Button == MouseButtons.Right)
					{
						Vector3 oCP = Camera.SelectedCameraPosition;
						//Vector3 oCP = SelectedLookAtPosition;
						Vector3 nCp = new Vector3(oCP.X + dx * pixelsPerUnit, oCP.Y + dy * pixelsPerUnit, oCP.Z);
						Camera.SelectedCameraPosition = nCp;
						Camera.Selected.UpdateCameraPositionControls();
						Camera.Selected.SetViewTransform();
					}
					//should make it so you can switch to spherical mode and use a selected sphere to rotate the camera or any other selected object.
					else if (e.Button == MouseButtons.Middle)
					{
						//Spherical3 sp = Camera.Selected.GetSpherical();
						////(x = cameraTheta, y = cameraPhi, z = cameraRho);
						////double cameraRho = sp.Z + new Vector2(dx, dy).Length();
						//float cameraRho = (float)sp.Rho;
						//double twoPi = 2d * Math.PI;
						////float dz = Camera.SphericalToCartesian(
						//Spherical3 DSp = Camera.CartesianToSpherical(dx, dy, cameraRho);
						//float cameraTheta = (float)Math.IEEERemainder((double)(sp.Theta + DSp.Theta * RadianFactor), twoPi);   //  / cameraRho
						//float cameraPhi = (float)Math.IEEERemainder((double)(sp.Phi + DSp.Phi * RadianFactor), twoPi);
						//Camera.Selected.CameraPosition = Camera.SphericalToCartesian(cameraTheta, cameraPhi, cameraRho);
						//Camera.Selected.UpdateCameraPositionControls();
						//Camera.Selected.SetViewTransform();
					}

				}
			}


			mouseLast = new Point(e.X, e.Y);
			lastButton = e.Button;

			FireMouseEvent(e);
		}

		private void SetTextCoordinates(MouseEventArgs e)
		{
			//set mouse pixel coordinates on display screen
			tbCoordinates.Text = "Mouse Position = (" + e.X + ", " + e.Y + ")";

		}


		////Taken from: http://forums.xna.com/forums/p/10940/57432.aspx
		//public static Vector3 Unproject(Camera camera, Vector3 screenSpace, Matrix modelViewMatrix)
		//{
		//    //First, convert raw screen coords to unprojectable ones  
		//    Viewport port = MainProgram.DxDevice.Viewport;//camera.Viewport;
		//    Vector3 position = new Vector3();
		//    position.X = (((screenSpace.X - (float)port.X) / ((float)port.Width)) * 2f) - 1f;
		//    position.Y = -((((screenSpace.Y - (float)port.Y) / ((float)port.Height)) * 2f) - 1f);
		//    position.Z = (screenSpace.Z - port.MinZ) / (port.MaxZ - port.MinZ);

		//    //Unproject by transforming the 4d vector by the inverse of the projecttion matrix, followed by the inverse of the view matrix.  
		//    Vector4 us4 = new Vector4(position.X, position.Y, position.Z, 1f);
			
		//    Matrix projectionMatrixInverse = MainProgram.DxDevice.Transform.Projection;
		//        projectionMatrixInverse.Invert();

		//    Vector4 up4 = Vector4.Transform(us4, projectionMatrixInverse);
		//    Vector3 up3 = new Vector3(up4.X, up4.Y, up4.Z);
		//    //up3.Scale(1f / up4.W); //better to do this here to reduce precision loss..  

		//    Matrix cameraViewMatrixInverse =
		//        Matrix.LookAtLH(camera.CameraPosition,
		//        camera.CameraTarget,
		//        camera.CameraUpVector);

		//        cameraViewMatrixInverse.Invert();

		//    Vector3 uv3 = Vector3.TransformCoordinate(up3, cameraViewMatrixInverse);
		//    Matrix Transform = modelViewMatrix;//unModelView.Invert();
		//    uv3.TransformCoordinate(Transform);
		//    return (uv3);
		//}

		public void mFrm_MouseWheel(object sender, MouseEventArgs e)
		{
			Spherical3 vSpherical = Camera.DefaultCamera.CameraPositionSpherical(); //((float)cameraTheta, (float)cameraPhi, (float)cameraRho);

			float newRho;
			float delta = (float)(e.Delta) * this.MouseRhoScalingFactor;

			newRho = (float) vSpherical.Rho - delta;
			Camera.DefaultCamera.CameraPosition = Camera.SphericalToCartesian(
				vSpherical.Theta, 
				vSpherical.Phi, 
				newRho);
			Camera.DefaultCamera.UpdateCameraPositionControls();
			Camera.DefaultCamera.SetViewTransform();
			FireMouseEvent(e);
		}

		private float max(float p, float p2)
		{
			return p > p2 ? p : p2;
		}

		private void FireMouseEvent(MouseEventArgs e)
		{
			SetTextCoordinates(e);
			vudObjectMouseRay.Value = this.SelectMesh_Intersection;

			if (MouseEvent != null) MouseEvent.Invoke(this, e);
		}
																	
		public void mFrm_MouseDown(object sender, MouseEventArgs e)
		{
			//should 
			FireMouseEvent(e);
		}

		private void MouseForm_FormClosing(object sender, FormClosingEventArgs e)
		{
			if (e.CloseReason == CloseReason.UserClosing)
				e.Cancel = true;
			this.Hide();
			
		}

		private void MouseForm_Activated(object sender, EventArgs e)
		{
			cboCameras.DataSource = Camera.CameraForms;
			cboCameras.DisplayMember = "DisplayName";
			pgMouse.SelectedObject = this;

		}

		private void cbMoveCameraWithMouse_CheckedChanged(object sender, EventArgs e)
		{
			this.MoveCameraPositionWithMouse = cbMoveCameraWithMouse.Checked;
		}	
	}
}